{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.optim as optim\n",
    "from torch.utils.data import DataLoader\n",
    "from torchvision import datasets, transforms"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 定义图像预处理方式,ToTensor() + Normalize()\n",
    "transform=transforms.Compose([\n",
    "    transforms.ToTensor(),\n",
    "    transforms.Normalize((0.5,), (0.5,))  # 简单归一化到[-1,1]\n",
    "])\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz\n",
      "Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz to ./data/FashionMNIST/raw/train-images-idx3-ubyte.gz\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100.0%\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Extracting ./data/FashionMNIST/raw/train-images-idx3-ubyte.gz to ./data/FashionMNIST/raw\n",
      "\n",
      "Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz\n",
      "Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz to ./data/FashionMNIST/raw/train-labels-idx1-ubyte.gz\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100.0%\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Extracting ./data/FashionMNIST/raw/train-labels-idx1-ubyte.gz to ./data/FashionMNIST/raw\n",
      "\n",
      "Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gz\n",
      "Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gz to ./data/FashionMNIST/raw/t10k-images-idx3-ubyte.gz\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100.0%\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Extracting ./data/FashionMNIST/raw/t10k-images-idx3-ubyte.gz to ./data/FashionMNIST/raw\n",
      "\n",
      "Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz\n",
      "Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz to ./data/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100.0%"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Extracting ./data/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz to ./data/FashionMNIST/raw\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "# 下载并加载训练集 测试集\n",
    "\n",
    "train_dataset=datasets.FashionMNIST(\n",
    "    root='./data',\n",
    "    train=True,\n",
    "    download=True,\n",
    "    transform=transform\n",
    ")\n",
    "\n",
    "test_dataset=datasets.FashionMNIST(\n",
    "    root='./data',\n",
    "    train=False,\n",
    "    download=True,\n",
    "    transform=transform\n",
    ")\n",
    "\n",
    "train_loader=DataLoader(train_dataset,batch_size=64,shuffle=True)\n",
    "test_loader=DataLoader(test_dataset,batch_size=1000,shuffle=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "class SimpleCNN(nn.Module):\n",
    "    def __init__(self):\n",
    "        super(SimpleCNN,self).__init__()\n",
    "        # 第一层卷积:输入通道=1(灰度图),输出通道=16,卷积核大小=3x3\n",
    "        self.conv1=nn.Conv2d(in_channels=1,out_channels=16,kernel_size=3)\n",
    "        # 第二层卷积:输入通道=16,输出通道=32,卷积核大小=3x3\n",
    "        self.conv2=nn.Conv2d(in_channels=16,out_channels=32,kernel_size=3)\n",
    "\n",
    "        # 池化层\n",
    "        self.pool=nn.MaxPool2d(kernel_size=2,stride=2)\n",
    "\n",
    "        # 全连接层: 需要先确定展平后特征图的大小\n",
    "        self.fc1=nn.Linear(32*5*5,128)\n",
    "        self.fc2=nn.Linear(128,10)\n",
    "\n",
    "        # 激活函数\n",
    "        self.relu=nn.ReLU()\n",
    "    \n",
    "    def forward(self,x):\n",
    "        # 输入x的大小为[batch_size,1,28,28]\n",
    "        x=self.relu(self.conv1(x))  # [batch_size,16,26,26]\n",
    "        x=self.pool(x)              # [batch_size,16,13,13]\n",
    "        x=self.relu(self.conv2(x))  # [batch_size,32,11,11]\n",
    "        x=self.pool(x)              # [batch_size,32,5,5]\n",
    "\n",
    "        # 展平\n",
    "        x=x.view(x.size(0),-1)      # [batch_size,32*5*5]\n",
    "\n",
    "        x=self.relu(self.fc1(x))    # [batch_size,128]\n",
    "        x=self.fc2(x)               # [batch_size,10]\n",
    "        return x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch [1/10], Loss: 1.1967\n",
      "Accuracy on test set: 0.7360\n",
      "Epoch [2/10], Loss: 0.5844\n",
      "Accuracy on test set: 0.7899\n",
      "Epoch [3/10], Loss: 0.5149\n",
      "Accuracy on test set: 0.8063\n",
      "Epoch [4/10], Loss: 0.4722\n",
      "Accuracy on test set: 0.7990\n",
      "Epoch [5/10], Loss: 0.4402\n",
      "Accuracy on test set: 0.7967\n",
      "Epoch [6/10], Loss: 0.4173\n",
      "Accuracy on test set: 0.8387\n",
      "Epoch [7/10], Loss: 0.3987\n",
      "Accuracy on test set: 0.8473\n",
      "Epoch [8/10], Loss: 0.3823\n",
      "Accuracy on test set: 0.8549\n",
      "Epoch [9/10], Loss: 0.3689\n",
      "Accuracy on test set: 0.8534\n",
      "Epoch [10/10], Loss: 0.3572\n",
      "Accuracy on test set: 0.8512\n"
     ]
    }
   ],
   "source": [
    "def train_and_eval(model,train_loader,test_loader,epochs=10,lr=0.01):\n",
    "    # 定义损失函数和优化器\n",
    "    criterion=nn.CrossEntropyLoss()\n",
    "    optimizer=optim.SGD(model.parameters(),lr=lr)\n",
    "\n",
    "    for epoch in range(epochs):\n",
    "        model.train()  # 训练模式\n",
    "        running_loss=0.0\n",
    "\n",
    "        for images,labels in train_loader:\n",
    "            # 梯度清0\n",
    "            optimizer.zero_grad()\n",
    "\n",
    "            # 前向传播\n",
    "            outputs=model(images)\n",
    "            loss=criterion(outputs,labels)\n",
    "\n",
    "            # 反向传播\n",
    "            loss.backward()\n",
    "\n",
    "            # 参数更新\n",
    "            optimizer.step()\n",
    "            running_loss+=loss.item()\n",
    "        \n",
    "        # 打印训练过程\n",
    "        print(f\"Epoch [{epoch+1}/{epochs}], Loss: {running_loss/len(train_loader):.4f}\")\n",
    "\n",
    "\n",
    "        # 模型评估\n",
    "        model.eval()\n",
    "        correct=0\n",
    "        total=0\n",
    "\n",
    "        with torch.no_grad():\n",
    "            for images,label in test_loader:\n",
    "                outputs=model(images)\n",
    "                _,predicted=torch.max(outputs,1)\n",
    "                total+=label.size(0)\n",
    "                correct+=(predicted==label).sum().item()\n",
    "        accuracy=correct/total\n",
    "        print(f\"Accuracy on test set: {accuracy:.4f}\")\n",
    "\n",
    "\n",
    "# 实例化模型并训练模型\n",
    "cnn=SimpleCNN()\n",
    "train_and_eval(cnn,train_loader,test_loader,epochs=10,lr=0.01)    \n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "llm-learning",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.16"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
